﻿using System;
using System.Collections.Generic;

namespace Borg.Framework.CompositionBinding
{
    public class PlugBehavior : ICompositionBehavior<PlugInfo>
    {
        private readonly List<PlugInfo> _items;
        private SlotInfo _slot;

        public event EventHandler<ItemEventArgs<PlugInfo>> ItemAdded;
        public event EventHandler<ItemEventArgs<PlugInfo>> ItemRemoved;

        public event EventHandler<ItemEventArgs<PlugInfo>> ItemComposed;
        public event EventHandler<ItemEventArgs<PlugInfo>> ItemDecomposed;

        public PlugBehavior()
        {
            _items = new List<PlugInfo>();
        }

        public PlugBehavior(SlotInfo slot)
        {
            _items = new List<PlugInfo>();
            Slot = slot;
        }

        private void Add(PlugInfo item)
        {
            _items.Add(item);
            OnEvent(ItemAdded, new ItemEventArgs<PlugInfo>(item));
        }

        public void Compose(PlugInfo item)
        {
            if (_slot != null && !_slot.PluggedPlugInfos.Contains(item))
                _slot.Plug(item);
        }

        public void Decompose(PlugInfo item)
        {
            if (_slot != null && _slot.PluggedPlugInfos.Contains(item))
                _slot.Unplug(item);
        }

        private void OnCreated(object sender, ExtensionEventArgs args)
        {
            Add(args.ExtensionInfo.PlugInfos[_slot.Name]);
        }

        private void OnDeregistered(object sender, RegisterEventArgs args)
        {
            foreach (var ei in args.PlugTypeInfo.ExtensionTypeInfo.ExtensionInfos)
                Remove(ei.PlugInfos[args.PlugTypeInfo.Name]);

            args.PlugTypeInfo.ExtensionTypeInfo.ExtensionCreated -= OnCreated;
            args.PlugTypeInfo.ExtensionTypeInfo.ExtensionReleased -= OnReleased;
        }

        private void OnEvent(EventHandler<ItemEventArgs<PlugInfo>> handler, ItemEventArgs<PlugInfo> args)
        {
            if (handler != null)
                handler(this, args);
        }

        private void OnPlugged(object sender, PlugEventArgs args)
        {
            OnEvent(ItemComposed, new ItemEventArgs<PlugInfo>(args.PlugInfo));
        }

        private void OnRegistered(object sender, RegisterEventArgs args)
        {
            foreach (var ei in args.PlugTypeInfo.ExtensionTypeInfo.ExtensionInfos)
                Add(ei.PlugInfos[args.PlugTypeInfo.Name]);

            args.PlugTypeInfo.ExtensionTypeInfo.ExtensionCreated += OnCreated;
            args.PlugTypeInfo.ExtensionTypeInfo.ExtensionReleased += OnReleased;
        }

        private void OnReleased(object sender, ExtensionEventArgs args)
        {
            Remove(args.ExtensionInfo.PlugInfos[_slot.Name]);
        }

        private void OnUnplugged(object sender, PlugEventArgs args)
        {
            OnEvent(ItemDecomposed, new ItemEventArgs<PlugInfo>(args.PlugInfo));
        }

        private void Remove(PlugInfo item)
        {
            _items.Remove(item);
            OnEvent(ItemRemoved, new ItemEventArgs<PlugInfo>(item));
        }

        public IEnumerable<PlugInfo> ComposedItems
        {
            get
            {
                return (_slot != null
                    ? _slot.PluggedPlugInfos
                    : (IEnumerable<PlugInfo>)new List<PlugInfo>());
            }
        }

        public IEnumerable<PlugInfo> Items
        {
            get { return _items; }
        }

        public SlotInfo Slot
        {
            get { return _slot; }
            set
            {
                if (_slot == value)
                    return;

                if (_slot != null)
                {
                    _slot.Registered -= OnRegistered;
                    _slot.Deregistered -= OnDeregistered;
                    _slot.Plugged -= OnPlugged;
                    _slot.Unplugged -= OnUnplugged;
                    
                    foreach (var pti in _slot.RegisteredPlugTypeInfos)
                    {
                        pti.ExtensionTypeInfo.ExtensionCreated -= OnCreated;
                        pti.ExtensionTypeInfo.ExtensionReleased -= OnReleased;

                        foreach (var pi in _slot.PluggedPlugInfos)
                            OnEvent(ItemDecomposed, new ItemEventArgs<PlugInfo>(pi));

                        foreach (var ei in pti.ExtensionTypeInfo.ExtensionInfos)
                            Remove(ei.PlugInfos[pti.Name]);
                    }
                }

                _slot = value;

                if (_slot == null)
                    return;

                _slot.AutoPlug = false;

                if (!_slot.IsOpen)
                    _slot.Open();

                _slot.Registered += OnRegistered;
                _slot.Deregistered += OnDeregistered;
                _slot.Plugged += OnPlugged;
                _slot.Unplugged += OnUnplugged;
                
                foreach (var pti in _slot.RegisteredPlugTypeInfos)
                {
                    foreach (var ei in pti.ExtensionTypeInfo.ExtensionInfos)
                        Add(ei.PlugInfos[pti.Name]);

                    foreach (var pi in _slot.PluggedPlugInfos)
                        OnEvent(ItemComposed, new ItemEventArgs<PlugInfo>(pi));

                    pti.ExtensionTypeInfo.ExtensionCreated += OnCreated;
                    pti.ExtensionTypeInfo.ExtensionReleased += OnReleased;
                }
            }
        }
    }
}

